use crate::{config::Name, Config};

use super::Class;

#[derive(Clone)]
pub struct Options<'a> {
	pub(crate) cfg: &'a Config,
}

pub struct FmtClass<'a> {
	class: &'a Class,
	options: Options<'a>,
	this_: Name<'a>,
	super_: Name<'a>,
}
impl<'a> FmtClass<'a> {
	pub fn new(class: &'a Class, options: Options<'a>) -> Option<Self> {
		Some(Self {
			class,
			this_: options.cfg.name(&class.this_),
			super_: options.cfg.name(&class.super_),
			options,
		})
	}
}
impl<'a> std::fmt::Display for FmtClass<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		let this_ = &self.this_.to_rust();
		let super_ = &self.super_.to_rust();
		f.write_fmt(format_args!(
			r##"
// This file has been automatically generated by `jnim` from `{this_}.class`.
// DO NOT EDIT

use jnim::*;

{super_use}
{faces_use}

pub struct {this_} {{
	__object: JObject,
	__supper: {super_},{faces_field}
}}
impl JNameMaker for {this_} {{
	const JAVA_PATH: &'static [u8] = b"{java_full}\0";
}}
impl JPlainMarker for {this_} {{
	fn class(env: Option<&JEnv>) -> Option<JClassID> {{
		static CACHE: CachedID = CachedID::new();
		CACHE.get(|| JEnv::env(env)?.find_class(Self::JAVA_PATH))
	}}
}}
{as_ref_impl}

// impl interfaces
{faces_impl}

// field
impl {this_} {{
{constant_fields}
{fields}
{fields_static}
}}

// method
impl {this_} {{
{static_method}
{virtual_method}
}}

#[test]
fn test_type_zero() {{
	assert_eq!(0, std::mem::size_of::<{this_}>());
}}

"##,
			super_use = self.super_.fmt_use(),
			java_full = self.this_.as_java(),
			faces_use = self.class.fmt_faces_use(true),
			faces_field = self.class.fmt_faces_field(),
			faces_impl = self.class.fmt_faces_impl(this_, true),
			as_ref_impl = FmtAsRef(this_, super_),
			constant_fields = self.class.fmt_consts("pub "),
			fields = self.class.fmt_fields("pub ", "class", self.options.cfg),
			fields_static = self.class.fmt_fields_static("pub ", "class", self.options.cfg),
			virtual_method = self.class.fmt_methods("pub ", "class", self.options.cfg),
			static_method = self.class.fmt_methods_static("pub ", "class", self.options.cfg)
		))?;
		Ok(())
	}
}

pub struct FmtAsRef<'a>(&'a str, &'a str);
impl<'a> std::fmt::Display for FmtAsRef<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.write_fmt(format_args!(
			r##"
impl core::ops::Deref for {this_} {{
	type Target = {super_};
	fn deref(&self) -> &Self::Target {{
		&self.__supper
	}}
}}
impl AsRef<{super_}> for {this_} {{
	fn as_ref(&self) -> &{super_} {{
		&self.__supper
	}}
}}"##,
			this_ = self.0,
			super_ = self.1,
		))?;
		if self.1 != "JObject" {
			f.write_fmt(format_args!(
				r##"
impl AsRef<JObject> for {this_} {{
	fn as_ref(&self) -> &JObject {{
		&self.__object
	}}
}}"##,
				this_ = self.0,
			))?;
		}
		Ok(())
	}
}
